iT邦幫忙

2024 iThome 鐵人賽

DAY 15
0

前言

本篇文章會將剩下的不會完成,最後會使用Postman 測試整個後端系統

JwtAuthenticationFilter

這裡使用到 HttpServletRequestHttpServletResponse,這兩個可以在用戶發起請求時,獲取所有相關的資料,包括:「用戶 IP、用戶請求的方法...」,詳細的項目有附在程式碼裡面

在 介紹 JWT 的文章中有提到 JWT 的組成,在這裡我們便是將 JWT 慢慢拆解,從標頭的 Authorization,接著將會放在 JWT 前面的 Bearer 拿掉,剩下的內容就會去比對資料庫中的資料,如果都是正常的那麼這個用戶發起的請求就會讓他繼續執行下去,但是如果有任何一個地方有問題,就會馬上被 Filter給篩掉

@Slf4j
@Component // 標記為 Spring 組件,以便在 Spring 容器中進行管理和自動裝配
@RequiredArgsConstructor 
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    private final JwtService jwtService; 
    private final UserDetailsService userDetailsService; 

    @Override
    protected void doFilterInternal(
            //標記為不可為空值
            //request、response會在使用者發起HTTP請求時自動獲取相關資料
            @NonNull HttpServletRequest request,
            @NonNull HttpServletResponse response,
            @NonNull FilterChain filterChain
    ) throws ServletException, IOException {
        // 從 HTTP 請求中獲取 Authorization 頭部
        //範例: Authorization: Bearer eyJhbGciOiJIUzI1NiIsI...
        final String authHeader = request.getHeader("Authorization");
        //用來存取Bearer後面的JWT(JSON WEB TOKEN)
        final String jwt;
        //存取JWT解析後的userEmail
        final String userEmail;

//        log.info("requestURL :" + request.getRequestURL().toString());
//        log.info("requestURI :" + request.getRequestURI());
//        log.info("URL地址中附帶的參數 :" + request.getQueryString());
//        log.info("來訪者的IP位址 :" + request.getRemoteAddr());
//        log.info("RemoteUser :" + request.getRemoteUser());
//        log.info("來訪者主機名 :" + request.getRemoteHost());
//        log.info("來訪者Port號 :" + request.getRemotePort());
//        log.info("請求URL地址用的方法 :" + request.getMethod());
//        log.info("PathInfo :" + request.getPathInfo());
//        log.info("Web服務端IP地址 :" + request.getLocalAddr());
//        log.info("Web服務端主機名 :" + request.getLocalName());

        // 檢查 Authorization 頭部是否存在且格式正確
        if (authHeader == null || !authHeader.startsWith("Bearer ")){
            // 如果不符合,則直接將目前的請求傳遞給下一個過濾器
            filterChain.doFilter(request, response);
            return;
        }
        // 從 Authorization 頭部中提取 JWT token
        //範例: Authorization: Bearer eyJhbGciOiJIUzI1NiIsI...
        //擷取索引值為7之後的資料,因此會將「 Bearer 」這一段去掉,留下剩下的JWT
        jwt = authHeader.substring(7);
        // 使用 JwtService 從 token 中提取用戶名
        userEmail = jwtService.extractUserName(jwt);

        // 檢查用戶是否已經被驗證
        if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null){
            // 加載用戶詳細信息
            UserDetails userDetails = this.userDetailsService.loadUserByUsername(userEmail);
            // 驗證 token 是否有效
            if (jwtService.isTokenValid(jwt, userDetails)){
                // 創建身份驗證令牌:
                UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
                        //用戶資料
                        userDetails,
                        //額外辨識憑證
                        null,
                        //用戶角色權限
                        userDetails.getAuthorities()
                );
                // 設置用戶的身份驗證詳細訊息
                authToken.setDetails(
                        new WebAuthenticationDetailsSource().buildDetails(request)
                );
                // 將身份驗證令牌設置到 SecurityContext
                SecurityContextHolder.getContext().setAuthentication(authToken);
            }
        }
        // 繼續處理請求
        filterChain.doFilter(request, response);
    }
}

SecurityConfiguration

在這裡我們要做的工作就是規定用戶的角色權限,比如只有admin可以建立、刪除,user只能查看資料等等,也就是白名單的功能

可以注意到白名單路徑的部分,路徑是寫成 "/api/auth/**" ,那個 ** 的意思,就是指 /apu/auth 底下的所有路徑

@Configuration // 表示此類為配置類
@EnableWebSecurity // 啟用 Spring Security 的 Web 安全支持
@RequiredArgsConstructor // Lombok 注解,自動生成含有所有 final 字段的構造函數
public class SecurityConfiguration {

    //添加白名單,在這個路徑底下的要求都不用Token
    private static final String WHITE_LIST_URL = "/api/auth/**";

////   可以將所有不須經過Token的路徑用List的方式一次輸入
//    private static final String[] WHITE_LIST_URL = {"/api/v1/auth/**",
//    "api/v1/user/**"};

    private final JwtAuthenticationFilter jwtAuthFilter; // JWT 身份驗證過濾器
    private final AuthenticationProvider authenticationProvider; // 自定義身份驗證提供者

    @Bean // 定義一個 Bean,Spring 容器會管理其生命周期
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .csrf(AbstractHttpConfigurer::disable) // 禁用 CSRF 防護,CSRF防護要求用戶在請求的時候需要將創建時的Token一起傳入,經過認證後才可以使用請求
                .authorizeHttpRequests(req -> // 開始配置請求的授權規則
                        req.requestMatchers(WHITE_LIST_URL) // 對於白名單的 URL
                                .permitAll()                // 允許所有請求,不需要Token即可使用
                                //針對特定的URL設定需要的權限
                                .requestMatchers(DELETE,"api/v1/user/**").hasAnyAuthority("admin") // 只有 admin 權限可以執行 DELETE 操作
                                .requestMatchers(POST,"api/v1/user/**").hasAnyAuthority("admin") // 只有 admin 權限可以執行 POST 操作
                                .requestMatchers(PUT,"api/v1/user/**").hasAnyAuthority("admin") // 只有 admin 權限可以執行 PUT 操作
                                .requestMatchers(GET,"api/v1/user/**").hasAnyAuthority("admin", "user") // admin 和 user 權限都可以執行 GET 操作
                                //任何請求代表其他沒有特別設定權限的位置
                                .anyRequest() // 任何其他請求
                                .authenticated() // 都需要通過身份驗證
                )
                .sessionManagement(session -> session.sessionCreationPolicy(STATELESS)) // 設置為無狀態會話
                .authenticationProvider(authenticationProvider) // 設置自定義的身份驗證提供者
                .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class); // 在 UsernamePasswordAuthenticationFilter 之前添加 JWT 過濾器

        return http.build(); // 建立並返回 SecurityFilterChain 實例
    }
}

測試效果


上一篇
[DAY 14] 後端結合身分驗證系統 (4)
下一篇
[DAY16] Semantic Kernel 介紹
系列文
智慧語義互動平台:基於Spring和Semantic Kernel的Android應用創新20
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言